package com.ruoyi.work.utils;

import com.itextpdf.awt.geom.Rectangle2D;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import com.itextpdf.text.pdf.security.*;
import com.ruoyi.work.config.PdfSignatureConfig;
import com.ruoyi.work.param.PdfAddContentParam;

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.Calendar;
import java.util.List;
import java.util.UUID;

/**
 * PDF工具类，提供 水印、签名、盖章、骑缝章 功能
 *
 * @author fengcheng
 */
public class PdfUtils {
    /**
     * 往PDF上添加文字水印
     *
     * @param inputFile     原文件
     * @param outputFile    加水印后的文件
     * @param waterMarkName 水印字符
     */
    public static void addWaterMark(String inputFile, String outputFile, String waterMarkName) {
        PdfReader reader = null;
        PdfStamper stamper = null;
        try {
            reader = new PdfReader(inputFile);
            stamper = new PdfStamper(reader, Files.newOutputStream(Paths.get(outputFile)));
            //水印字体，放到服务器上对应文件夹下（arial中文不生效）
            //BaseFont base = BaseFont.createFont("D:/workspace/springboot/src/main/resources/arial.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
            BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
            Rectangle pageRect;
            PdfGState gs = new PdfGState();
            //填充不透明度  为1完全不透明
            gs.setFillOpacity(0.1f);
            //笔画不透明度， 为1完全不透明
            gs.setStrokeOpacity(0.1f);
            int total = reader.getNumberOfPages() + 1;
            JLabel label = new JLabel();
            FontMetrics metrics;
            int textH;
            int textW;
            label.setText(waterMarkName);
            metrics = label.getFontMetrics(label.getFont());
            textH = metrics.getHeight();
            textW = metrics.stringWidth(label.getText());
            PdfContentByte under;
            int interval = 0;
            for (int i = 1; i < total; i++) {
                pageRect = reader.getPageSizeWithRotation(i);
                //在字体下方加水印
                under = stamper.getOverContent(i);
                under.beginText();
                under.saveState();
                under.setGState(gs);
                //这里修改水印字体大小
                under.setFontAndSize(base, 36);
                //这里设置字体上下间隔
                for (int height = interval + textH; height < pageRect.getHeight(); height = height + textH * 9) {
                    //这里设置字体左右间隔
                    for (int width = interval + textW; width < pageRect.getWidth() + textW; width = width + textW * 4) {
                        //这里设置倾斜角度
                        under.showTextAligned(Element.ALIGN_LEFT, waterMarkName, width - textW, height - textH, 45);
                    }
                }
                under.endText();
            }
            stamper.close();
            reader.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (stamper != null) {
                    stamper.close();
                }
            } catch (IOException | DocumentException e) {
                e.printStackTrace();
            }
            if (reader != null) {
                reader.close();
            }
        }
    }

    /**
     * 根据指定坐标往PDF上添加文字内容
     *
     * @param filePath            原文件路径
     * @param pdfAddContentParams 实体类
     */
    public static byte[] addText(String filePath, List<PdfAddContentParam> pdfAddContentParams) throws Exception {
        PdfReader pdfReader = new PdfReader(filePath);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //设置输入文件以及输出文件地址
        PdfStamper stamper = new PdfStamper(pdfReader, byteArrayOutputStream);
        //设置字体
        BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        for (PdfAddContentParam pdfAddContentParam : pdfAddContentParams) {
            Font font = new Font(baseFont, 10);
            if (pdfAddContentParam.getPageNum() > pdfReader.getNumberOfPages()) {
                throw new RuntimeException("设置的页码数[" + pdfAddContentParam.getPageNum() + "]大于原文件页码数[" + pdfReader.getNumberOfPages() + "]！请重新输入");
            }
            PdfContentByte overContent = stamper.getOverContent(pdfAddContentParam.getPageNum());
            ColumnText columnText = new ColumnText(overContent);
            columnText.setSimpleColumn(pdfAddContentParam.getLlx(), pdfAddContentParam.getLly(), pdfAddContentParam.getUrx(), pdfAddContentParam.getUry());
            Paragraph elements = new Paragraph(pdfAddContentParam.getContent());
            elements.setFont(font);
            columnText.addElement(elements);
            columnText.go();
        }
        stamper.close();
        return byteArrayOutputStream.toByteArray();
    }

    /**
     * 根据关键字往PDF上添加文字内容
     *
     * @param filePath            原文件路径
     * @param pdfAddContentParams 实体类
     */
    public static byte[] addTextByKeyword(String filePath, List<PdfAddContentParam> pdfAddContentParams) throws Exception {
        PdfReader pdfReader = new PdfReader(filePath);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        //设置输入文件以及输出文件地址
        PdfStamper stamper = new PdfStamper(pdfReader, byteArrayOutputStream);
        // 获取PDF文件的总页数
        int pageNum = pdfReader.getNumberOfPages();
        PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(pdfReader);
        for (PdfAddContentParam pdfAddContentParam : pdfAddContentParams) {
            StringBuilder stringBuilder = new StringBuilder();
            final boolean[] hasKeyword = {false, true};
            String keyword = pdfAddContentParam.getKeyword();
            int length = keyword.length();
            if (length == 0) {
                throw new RuntimeException("请输入关键字！");
            }
            for (int page = 1; page <= pageNum; page++) {
                if (hasKeyword[0]) {
                    break;
                }
                int finalPage = page;
                pdfReaderContentParser.processContent(page, new RenderListener() {
                    @Override
                    public void beginTextBlock() {
                    }


                    @Override
                    public void renderText(TextRenderInfo renderInfo) {
                        // 读取PDF文件的内容
                        String text = renderInfo.getText().trim();
                        stringBuilder.append(text);
                        if (stringBuilder.toString().contains(keyword)) {
                            Rectangle2D.Float boundingRectangle = renderInfo.getBaseline().getBoundingRectange();
                            if (hasKeyword[1]) {
                                // 关键字的坐标
                                double x = boundingRectangle.getX();
                                double y = boundingRectangle.getY();
                                //设置字体
                                BaseFont baseFont = null;
                                try {
                                    baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                                } catch (DocumentException e) {
                                    e.printStackTrace();
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                                Font font = new Font(baseFont, 10);
                                PdfContentByte overContent = stamper.getOverContent(finalPage);
                                ColumnText columnText = new ColumnText(overContent);
                                // 根据关键字的坐标计算+偏移量得到批注字体的坐标
                                columnText.setSimpleColumn((float) x + pdfAddContentParam.getLlx(), (float) y + pdfAddContentParam.getLly(), (float) x + 100 + pdfAddContentParam.getUrx(), (float) y + pdfAddContentParam.getUry());
                                Paragraph elements = new Paragraph(pdfAddContentParam.getContent());
                                elements.setFont(font);
                                columnText.addElement(elements);
                                try {
                                    columnText.go();
                                } catch (DocumentException e) {
                                    e.printStackTrace();
                                }
                                hasKeyword[1] = false;
                            }
                            hasKeyword[0] = true;
                        }
                        if (stringBuilder.toString().length() > length * 3) {
                            stringBuilder.setLength(0);
                        }
                    }

                    @Override
                    public void endTextBlock() {
                    }

                    @Override
                    public void renderImage(ImageRenderInfo renderInfo) {
                    }
                });
            }
        }
        stamper.close();
        pdfReader.close();
        return byteArrayOutputStream.toByteArray();
    }

    /**
     * pdf插入图片
     *
     * @param oldPath 插入图片前的pdf路径
     * @param newPath 插入图片后的pdf路径
     * @param imgPath 图片路径
     * @param x       x轴坐标
     * @param y       y轴坐标
     * @param width   图片宽度
     * @param height  图片高度
     * @param pageNum 当前页码
     * @throws IOException
     * @throws DocumentException
     */
    public static boolean addImage(String oldPath, String newPath, String imgPath, Float x, Float y, Integer width, Integer height, int pageNum) throws IOException, DocumentException {
        InputStream inputStream = Files.newInputStream(Paths.get(oldPath));
        FileOutputStream out = new FileOutputStream(newPath);
        PdfReader reader = new PdfReader(inputStream);
        //pdf页数
        int pdfPages = reader.getNumberOfPages();

        if (pageNum > pdfPages) {
            return false;
        }

        PdfStamper stamper = new PdfStamper(reader, out);
        //图片
        BufferedImage bufferedImage = ImageIO.read(Files.newInputStream(Paths.get(imgPath)));
        //图片放置的页码
        //图片处理
        Image img = Image.getInstance(ImageUtils.imageToBytes((bufferedImage), "png"));

        BufferedImage image = ImageIO.read(new File(imgPath));
        if (width == null || width == 0) {
            width = image.getWidth();
        }

        if (height == null || height == 0) {
            height = image.getHeight();
        }

        //设置图片大小
        img.scaleAbsolute(width, height);

        img.setTransparency(new int[0]);
        //设置图片位置
        img.setAbsolutePosition(x, y);
        stamper.getOverContent(pageNum).addImage(img);

        //关闭资源
        stamper.close();
        out.close();
        reader.close();
        return true;
    }


    /**
     * 获取文件后缀的方法
     *
     * @param filePath 文件路径
     * @return 文件后缀
     * @author fengcheng
     */
    private static String getFileExtension(String filePath) {
        File file = new File(filePath);
        String extension = "";
        try {
            if (file != null && file.exists()) {
                String name = file.getName();
                extension = name.substring(name.lastIndexOf(".") + 1);
            }
        } catch (Exception e) {
            extension = "";
        }
        return extension;
    }


    /**
     * 生成骑缝章
     *
     * @param oldPath   原pdf路径
     * @param newPath   新pdf路径
     * @param imagesUrl 印章路径
     * @param x         盖章x轴
     * @param y         盖章y轴
     * @param width     图片宽
     * @param height    图片高
     * @return 是否处理成功
     * @throws IOException
     */
    public static boolean generatePagingSeal(PdfSignatureConfig pdfSignatureConfig, String oldPath, String newPath, String imagesUrl, Float x, Float y, Float width, Float height, String storagePath) throws IOException {
        PdfReader pdfReader = new PdfReader(oldPath);
        int count = pdfReader.getNumberOfPages();
        if (count == 1) {
            return false;
        }
        pdfReader.close();
        String fileExtension = getFileExtension(imagesUrl);
        String name = new File(imagesUrl).getName();
        String nameWithoutExtension = name.substring(0, name.lastIndexOf("."));
        FileOutputStream fileOutputStream = null;
        try {

            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(pdfSignatureConfig.getPathInputStream(), pdfSignatureConfig.getPassword().toCharArray());
            String alias = (String) keyStore.aliases().nextElement();
            PrivateKey PrivateKey = (PrivateKey) keyStore.getKey(alias, pdfSignatureConfig.getPassword().toCharArray());
            Certificate[] chain = keyStore.getCertificateChain(alias);
            for (int i = 1; i <= count; i++) {
                String imageUrl = storagePath + "/sura/" + nameWithoutExtension + i + "." + fileExtension;
                PdfReader doc = new PdfReader(oldPath);
                Rectangle pageSize = doc.getPageSize(i);

                Float xAxle = (float) (pageSize.getWidth()) - width;
                if (y > (float) pageSize.getHeight()) {
                    return false;
                }

                FileUtils.createFile(newPath);
                fileOutputStream = new FileOutputStream(newPath);
                /**
                 * 1 参数依次为：文件名、文件输入流、文件版本号、临时文件、是否可以追加签名
                 *  1.1 false的话，pdf文件只允许被签名一次，多次签名，最后一次有效
                 *  1.2 true的话，pdf可以被追加签名，验签工具可以识别出每次签名之后文档是否被修改
                 */
                PdfStamper stamper = PdfStamper.createSignature(doc, fileOutputStream, '\0', null, true);


                PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
                appearance.setReason(pdfSignatureConfig.getCause());
                appearance.setLocation(pdfSignatureConfig.getPlace());
                appearance.setSignDate(Calendar.getInstance());
                appearance.setLayer2Text(pdfSignatureConfig.getPlace());
                appearance.setLayer2Text(pdfSignatureConfig.getName());

                appearance.setSignatureGraphic(Image.getInstance(imageUrl));
                /**
                 * 1 三个参数依次为：设置签名的位置、页码、签名域名称，多次追加签名的时候，签名域名称不能一样
                 * 1.1 签名的位置四个参数：印章左下角的X、Y轴坐标，印章右上角的X、Y轴坐标,
                 * 	这个位置是相对于PDF页面的位置坐标，即该坐标距PDF当前页左下角的坐标
                 */
                appearance.setVisibleSignature(new Rectangle(xAxle + 60, pageSize.getHeight() - height - y, xAxle + width + 20, pageSize.getHeight() - y), i, String.valueOf(UUID.randomUUID()));

                /**
                 * 设置认证等级，共4种，分别为：s
                 *  NOT_CERTIFIED、CERTIFIED_NO_CHANGES_ALLOWED、
                 *  CERTIFIED_FORM_FILLING 和 CERTIFIED_FORM_FILLING_AND_ANNOTATIONS
                 *
                 * 需要用哪一种根据业务流程自行选择
                 */
                appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);

                /**
                 * 印章的渲染方式，同样有4种：
                 *  DESCRIPTION、NAME_AND_DESCRIPTION,
                 *  GRAPHIC_AND_DESCRIPTION,GRAPHIC;
                 * 这里选择只显示印章
                 */
                appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

                /**
                 * 算法主要为：RSA、DSA、ECDSA
                 * 摘要算法,这里的itext提供了2个用于签名的接口，可以自己实现
                 */
                ExternalDigest digest = new BouncyCastleDigest();
                /**
                 * 签名算法，参数依次为：证书秘钥、摘要算法名称，例如MD5 | SHA-1 | SHA-2.... 以及 提供者
                 */
                ExternalSignature signature = new PrivateKeySignature(PrivateKey, DigestAlgorithms.SHA1, null);
                /**
                 * 最重要的来了,调用itext签名方法完成pdf签章
                 */
                MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);

                fileOutputStream.close();
                // 复制文件
                FileUtils.copyDir(newPath, oldPath);
                FileUtils.deleteFile(newPath);

                pdfReader.close();
                fileOutputStream.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 定义GetImage方法，根据PDF页数分割印章图片
     *
     * @param num PDF页数
     * @return
     * @throws IOException
     */
    public static BufferedImage[] getImage(String imagesUrl, int num, Integer width, Integer height) throws IOException {
        BufferedImage image = ImageIO.read(new File(imagesUrl));
        int rows = 1;
        int chunks = rows * num;
        if (width == null || width == 0) {
            width = image.getWidth() / num;
        }

        if (height == null || height == 0) {
            height = image.getHeight() / rows;
        }

        int count = 0;
        BufferedImage[] imgs = new BufferedImage[chunks];
        for (int x = 0; x < rows; x++) {
            for (int y = 0; y < num; y++) {
                imgs[count] = new BufferedImage(width, height, image.getType());
                Graphics2D gr = imgs[count++].createGraphics();
                gr.drawImage(image, 0, 0, width, height,
                        width * y, height * x,
                        width * y + width, height * x + height, Color.WHITE, null);
                gr.dispose();
            }
        }
        return imgs;
    }

    /**
     * pdf插入图片
     *
     * @param pdfSignatureConfig pdf签名配置
     * @param oldPath            插入图片前的pdf路径
     * @param newPath            插入图片后的pdf路径
     * @param imgPath            图片路径
     * @param x                  x轴坐标
     * @param y                  y轴坐标
     * @param width              图片宽度
     * @param height             图片高度
     * @param pageNum            当前页码
     * @throws IOException
     * @throws DocumentException
     */
    public static boolean stamp(PdfSignatureConfig pdfSignatureConfig, String oldPath, String newPath, String imgPath, Float x, Float y, Float width, Float height, int pageNum) {
        try {
            PdfReader pdfReader = new PdfReader(oldPath);
            int count = pdfReader.getNumberOfPages();
            if (pageNum > count) {
                return false;
            }
            FileUtils.createFile(newPath);
            FileOutputStream fileOutputStream = new FileOutputStream(newPath);
            /**
             * 1 参数依次为：文件名、文件输入流、文件版本号、临时文件、是否可以追加签名
             *  1.1 false的话，pdf文件只允许被签名一次，多次签名，最后一次有效
             *  1.2 true的话，pdf可以被追加签名，验签工具可以识别出每次签名之后文档是否被修改
             */
            PdfStamper stamper = PdfStamper.createSignature(pdfReader, fileOutputStream, '\0', null, true);

            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(pdfSignatureConfig.getPathInputStream(), pdfSignatureConfig.getPassword().toCharArray());
            String alias = keyStore.aliases().nextElement();
            PrivateKey PrivateKey = (PrivateKey) keyStore.getKey(alias, pdfSignatureConfig.getPassword().toCharArray());
            Certificate[] chain = keyStore.getCertificateChain(alias);

            // 获取数字签章属性对象，设定数字签章的属性
            PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
            appearance.setReason(pdfSignatureConfig.getCause());
            appearance.setLocation(pdfSignatureConfig.getPlace());
            appearance.setSignDate(Calendar.getInstance());
            appearance.setLayer2Text(pdfSignatureConfig.getPlace());
            appearance.setLayer2Text(pdfSignatureConfig.getName());

//            /**
//             * 用于盖章的印章图片，引包的时候要引入itext包的image
//             */
//            BufferedImage image = ImageIO.read(new File(imgPath));
//            if (width == null || width == 0) {
//                width = image.getWidth();
//            }
//
//            if (height == null || height == 0) {
//                height = image.getHeight();
//            }

            appearance.setSignatureGraphic(Image.getInstance(imgPath));

            Rectangle pageSize = pdfReader.getPageSize(pageNum);
            /**
             * 1 三个参数依次为：设置签名的位置、页码、签名域名称，多次追加签名的时候，签名域名称不能一样
             *  1.1 签名的位置四个参数：印章左下角的X、Y轴坐标，印章右上角的X、Y轴坐标,
             * 		这个位置是相对于PDF页面的位置坐标，即该坐标距PDF当前页左下角的坐标
             */
            appearance.setVisibleSignature(new Rectangle(x, pageSize.getHeight() - height - y, x + width, pageSize.getHeight() - y), pageNum, String.valueOf(UUID.randomUUID()));

            /**
             * 设置认证等级，共4种，分别为：s
             *  NOT_CERTIFIED、CERTIFIED_NO_CHANGES_ALLOWED、
             *  CERTIFIED_FORM_FILLING 和 CERTIFIED_FORM_FILLING_AND_ANNOTATIONS
             *
             * 需要用哪一种根据业务流程自行选择
             */
            appearance.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);

            /**
             * 印章的渲染方式，同样有4种：
             *  DESCRIPTION、NAME_AND_DESCRIPTION,
             *  GRAPHIC_AND_DESCRIPTION,GRAPHIC;
             * 这里选择只显示印章
             */
            appearance.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

            /**
             * 算法主要为：RSA、DSA、ECDSA
             * 摘要算法,这里的itext提供了2个用于签名的接口，可以自己实现
             */
            ExternalDigest digest = new BouncyCastleDigest();
            /**
             * 签名算法，参数依次为：证书秘钥、摘要算法名称，例如MD5 | SHA-1 | SHA-2.... 以及 提供者
             */
            ExternalSignature signature = new PrivateKeySignature(PrivateKey, DigestAlgorithms.SHA1, null);
            /**
             * 最重要的来了,调用itext签名方法完成pdf签章
             */
            MakeSignature.signDetached(appearance, digest, signature, chain, null, null, null, 0, MakeSignature.CryptoStandard.CMS);
            pdfReader.close();
            fileOutputStream.close();
            // 复制文件
            FileUtils.copyDir(newPath, oldPath);
            FileUtils.deleteFile(newPath);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;

    }
}
